# -*- coding: utf-8 -*-
"""
极度缩量股票筛选器
筛选规则：最新一日成交量是最近30天内每日成交量的最低值

这种极度缩量的情况可能预示着：
1. 股票处于底部，市场关注度极低
2. 变盘前的极度缩量，可能是突破前兆
3. 潜在的交易机会（极度缩量后往往会放量）

使用方法：
    python low_volume_screener.py              # 筛选所有股票
    python low_volume_screener.py --limit 100  # 限制筛选数量（测试用）
    python low_volume_screener.py --stock 000001  # 筛选指定股票
"""
import logging
import pandas as pd
import numpy as np
from datetime import datetime
import sys
import os
import argparse

# 添加父目录到路径
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from database import DatabaseManager, StockInfoDAO
from config import DATABASE_CONFIG
import warnings

# 忽略pandas警告
warnings.filterwarnings('ignore', category=pd.errors.SettingWithCopyWarning)

# 配置日志
# 获取项目根目录
PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
LOG_DIR = os.path.join(PROJECT_ROOT, 'logs')
os.makedirs(LOG_DIR, exist_ok=True)

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s [%(levelname)s] %(message)s',
    handlers=[
        logging.StreamHandler(),
        logging.FileHandler(os.path.join(LOG_DIR, 'low_volume_screener.log'), encoding='utf-8')
    ]
)
logger = logging.getLogger(__name__)


class LowVolumeScreener:
    """极度缩量股票筛选器"""
    
    def __init__(self):
        self.db = DatabaseManager(DATABASE_CONFIG)
        self.stock_dao = StockInfoDAO(self.db)
    
    def get_stock_codes(self, limit: int = None) -> list:
        """获取所有活跃股票代码"""
        try:
            sql = "SELECT stock_code, stock_name FROM new_stock_info WHERE status = 'active' ORDER BY stock_code"
            if limit:
                sql += f" LIMIT {limit}"
            
            results = self.db.execute_query(sql)
            return [(row['stock_code'], row['stock_name']) for row in results]
        except Exception as e:
            logger.error(f"获取股票列表失败: {e}")
            return []
    
    def get_stock_daily_data(self, stock_code: str, days: int = 35) -> pd.DataFrame:
        """
        获取股票最近N天的日线数据
        
        Args:
            stock_code: 股票代码
            days: 获取天数（默认35天，确保有足够的30个交易日数据）
            
        Returns:
            包含交易数据的DataFrame
        """
        sql = """
        SELECT trade_date, close_price, volume, amount, change_pct, turnover_rate
        FROM new_stock_daily 
        WHERE stock_code = %s 
        ORDER BY trade_date DESC 
        LIMIT %s
        """
        try:
            results = self.db.execute_query(sql, (stock_code, days))
            if not results:
                return pd.DataFrame()
            
            df = pd.DataFrame(results)
            df['trade_date'] = pd.to_datetime(df['trade_date'])
            
            # 转换数值列
            df['close_price'] = pd.to_numeric(df['close_price'], errors='coerce')
            df['volume'] = pd.to_numeric(df['volume'], errors='coerce')
            df['amount'] = pd.to_numeric(df['amount'], errors='coerce')
            df['change_pct'] = pd.to_numeric(df['change_pct'], errors='coerce')
            df['turnover_rate'] = pd.to_numeric(df['turnover_rate'], errors='coerce')
            
            # 按日期升序排列
            df = df.sort_values('trade_date').reset_index(drop=True)
            return df
            
        except Exception as e:
            logger.error(f"获取股票 {stock_code} 数据失败: {e}")
            return pd.DataFrame()
    
    def check_lowest_volume(self, df: pd.DataFrame, check_days: int = 30) -> dict:
        """
        检查最新一日成交量是否为最近N天内的最低值
        
        Args:
            df: 股票日线数据DataFrame
            check_days: 检查的天数（默认30天）
            
        Returns:
            符合条件返回详细信息字典，否则返回None
        """
        if df.empty or len(df) < check_days:
            return None
        
        # 获取最近check_days天的数据
        recent_data = df.tail(check_days).copy()
        
        # 检查是否有空值
        if recent_data['volume'].isna().any():
            return None
        
        # 获取最新一日的数据
        latest_row = recent_data.iloc[-1]
        latest_volume = latest_row['volume']
        latest_date = latest_row['trade_date']
        
        # 获取30天内的最小成交量
        min_volume = recent_data['volume'].min()
        
        # 检查最新一日成交量是否为最低值
        if latest_volume == min_volume:
            # 计算额外的统计信息
            avg_volume_30d = recent_data['volume'].mean()
            max_volume_30d = recent_data['volume'].max()
            
            # 计算成交量相对比例
            volume_ratio = latest_volume / avg_volume_30d if avg_volume_30d > 0 else 0
            
            # 计算30天内最低成交量出现的次数（是否持续缩量）
            min_volume_count = (recent_data['volume'] == min_volume).sum()
            
            # 计算最近5天的平均成交量（短期趋势）
            recent_5d_avg_volume = recent_data.tail(5)['volume'].mean()
            
            return {
                'trade_date': latest_date,
                'close_price': latest_row['close_price'],
                'volume': int(latest_volume),
                'amount': latest_row['amount'] if pd.notna(latest_row['amount']) else 0,
                'change_pct': latest_row['change_pct'] if pd.notna(latest_row['change_pct']) else 0,
                'turnover_rate': latest_row['turnover_rate'] if pd.notna(latest_row['turnover_rate']) else 0,
                'avg_volume_30d': int(avg_volume_30d),
                'max_volume_30d': int(max_volume_30d),
                'volume_ratio': round(volume_ratio, 4),  # 当前成交量/30日平均成交量
                'volume_shrink_pct': round((1 - volume_ratio) * 100, 2),  # 缩量百分比
                'min_volume_count': int(min_volume_count),  # 最低成交量出现次数
                'recent_5d_avg_volume': int(recent_5d_avg_volume),  # 最近5天平均成交量
                'is_continuous_low': min_volume_count > 1  # 是否持续在低位
            }
        
        return None
    
    def screen_stocks(self, stock_codes: list = None, limit: int = None) -> pd.DataFrame:
        """
        筛选符合条件的股票
        
        Args:
            stock_codes: 指定要筛选的股票列表，格式为[(code, name), ...]
            limit: 限制筛选的股票数量
            
        Returns:
            包含筛选结果的DataFrame
        """
        if stock_codes is None:
            stock_codes = self.get_stock_codes(limit=limit)
        
        if not stock_codes:
            logger.warning("没有找到需要筛选的股票")
            return pd.DataFrame()
        
        logger.info(f"开始筛选 {len(stock_codes)} 只股票...")
        
        results = []
        total = len(stock_codes)
        
        for idx, (stock_code, stock_name) in enumerate(stock_codes, 1):
            try:
                # 每10只股票输出一次进度
                if idx % 10 == 0 or idx == total:
                    logger.info(f"进度: {idx}/{total} ({idx/total*100:.1f}%)")
                
                # 获取日线数据
                df = self.get_stock_daily_data(stock_code, days=35)
                
                if df.empty or len(df) < 30:
                    continue
                
                # 检查是否满足条件
                result = self.check_lowest_volume(df, check_days=30)
                
                if result:
                    result['stock_code'] = stock_code
                    result['stock_name'] = stock_name
                    results.append(result)
                    
                    logger.info(
                        f"✓ {stock_code} {stock_name} - "
                        f"成交量: {result['volume']:,} (30日均值: {result['avg_volume_30d']:,}, "
                        f"缩量: {result['volume_shrink_pct']:.1f}%)"
                    )
            
            except Exception as e:
                logger.error(f"处理股票 {stock_code} {stock_name} 时出错: {e}")
                continue
        
        if not results:
            logger.info("未找到符合条件的股票")
            return pd.DataFrame()
        
        # 转换为DataFrame并排序
        df_results = pd.DataFrame(results)
        
        # 按缩量程度降序排列（缩量越多越靠前）
        df_results = df_results.sort_values('volume_shrink_pct', ascending=False)
        
        # 重新排列列的顺序
        columns_order = [
            'stock_code', 'stock_name', 'trade_date', 'close_price', 
            'volume', 'avg_volume_30d', 'max_volume_30d',
            'volume_ratio', 'volume_shrink_pct',
            'change_pct', 'turnover_rate', 'amount',
            'recent_5d_avg_volume', 'min_volume_count', 'is_continuous_low'
        ]
        df_results = df_results[columns_order]
        
        logger.info(f"\n筛选完成！共找到 {len(df_results)} 只符合条件的股票")
        
        return df_results
    
    def save_results(self, df: pd.DataFrame, output_dir: str = '.') -> tuple:
        """
        保存筛选结果到CSV和TXT文件
        
        Args:
            df: 筛选结果DataFrame
            output_dir: 输出目录
            
        Returns:
            (csv_file, txt_file) 文件路径元组
        """
        if df.empty:
            logger.warning("没有结果需要保存")
            return None, None
        
        timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
        csv_file = os.path.join(output_dir, f'low_volume_stocks_{timestamp}.csv')
        txt_file = os.path.join(output_dir, f'stock_codes_low_volume_{timestamp}.txt')
        
        try:
            # 保存CSV文件
            df.to_csv(csv_file, index=False, encoding='utf-8-sig')
            logger.info(f"结果已保存到: {csv_file}")
            
            # 保存股票代码到TXT文件
            with open(txt_file, 'w', encoding='utf-8') as f:
                for _, row in df.iterrows():
                    f.write(f"{row['stock_code']}\n")
            logger.info(f"股票代码已保存到: {txt_file}")
            
            return csv_file, txt_file
            
        except Exception as e:
            logger.error(f"保存结果失败: {e}")
            return None, None
    
    def print_summary(self, df: pd.DataFrame):
        """打印筛选结果摘要"""
        if df.empty:
            return
        
        logger.info("\n" + "="*80)
        logger.info("筛选结果摘要")
        logger.info("="*80)
        logger.info(f"符合条件的股票数量: {len(df)}")
        logger.info(f"平均缩量幅度: {df['volume_shrink_pct'].mean():.2f}%")
        logger.info(f"最大缩量幅度: {df['volume_shrink_pct'].max():.2f}%")
        logger.info(f"最小缩量幅度: {df['volume_shrink_pct'].min():.2f}%")
        
        # 统计持续低位的股票数量
        continuous_low_count = df['is_continuous_low'].sum()
        logger.info(f"持续在低位的股票: {continuous_low_count} 只")
        
        logger.info("\n前10只缩量最严重的股票:")
        logger.info("-"*80)
        for idx, row in df.head(10).iterrows():
            logger.info(
                f"{row['stock_code']} {row['stock_name']:8s} - "
                f"缩量: {row['volume_shrink_pct']:6.2f}% | "
                f"成交量: {row['volume']:12,} | "
                f"30日均: {row['avg_volume_30d']:12,} | "
                f"涨跌: {row['change_pct']:6.2f}%"
            )
        logger.info("="*80)
        
        # 输出逗号分隔的股票代码
        stock_codes = df['stock_code'].tolist()
        stock_codes_str = ','.join(stock_codes)
        
        logger.info("\n" + "="*80)
        logger.info("所有匹配的股票代码（逗号分隔）:")
        logger.info("="*80)
        logger.info(stock_codes_str)
        logger.info("="*80 + "\n")


def parse_arguments():
    """解析命令行参数"""
    parser = argparse.ArgumentParser(
        description='极度缩量股票筛选器 - 筛选最新一日成交量为30天内最低值的股票'
    )
    parser.add_argument(
        '--limit',
        type=int,
        help='限制筛选的股票数量（用于测试）'
    )
    parser.add_argument(
        '--stock',
        type=str,
        help='筛选指定股票代码'
    )
    parser.add_argument(
        '--output-dir',
        type=str,
        default='.',
        help='输出文件目录（默认：当前目录）'
    )
    
    return parser.parse_args()


def main():
    """主函数"""
    args = parse_arguments()
    
    logger.info("="*80)
    logger.info("极度缩量股票筛选器")
    logger.info("筛选规则: 最新一日成交量是最近30天内每日成交量的最低值")
    logger.info("="*80 + "\n")
    
    screener = LowVolumeScreener()
    
    # 如果指定了股票代码，只筛选该股票
    if args.stock:
        stock_info = screener.stock_dao.get_stock_info_by_code(args.stock)
        if not stock_info:
            logger.error(f"未找到股票: {args.stock}")
            return
        
        stock_codes = [(stock_info['stock_code'], stock_info['stock_name'])]
        logger.info(f"筛选指定股票: {args.stock} {stock_info['stock_name']}\n")
    else:
        stock_codes = None
    
    # 执行筛选
    start_time = datetime.now()
    df_results = screener.screen_stocks(stock_codes=stock_codes, limit=args.limit)
    end_time = datetime.now()
    
    # 打印结果摘要
    screener.print_summary(df_results)
    
    # 保存结果
    if not df_results.empty:
        csv_file, txt_file = screener.save_results(df_results, output_dir=args.output_dir)
        
        if csv_file and txt_file:
            logger.info(f"\n筛选用时: {(end_time - start_time).total_seconds():.2f} 秒")
            logger.info(f"详细结果请查看: {csv_file}")
            logger.info(f"股票代码列表: {txt_file}")
    
    logger.info("\n筛选完成！")


if __name__ == '__main__':
    main()

